﻿namespace Blaze.Security
{
    using System;
    using System.IO;
    using System.Text;
    using Ionic.Zip;

    /// <summary>
    /// 使用<see cref="BlowFish"/>解密后的<see cref="Stream"/>实现。
    /// </summary>
    public class DecryptionStream : Stream
    {
        public static byte[] Key = Encoding.ASCII.GetBytes("Blaze");
        public static byte Offset = 21;

        /// <summary>
        /// 当在派生类中重写时，获取指示当前流是否支持读取的值。
        /// </summary>
        /// <returns>
        /// 如果流支持读取，为 true；否则为 false。
        /// </returns>
        public override bool CanRead
        {
            get { return true; }
        }

        /// <summary>
        /// 当在派生类中重写时，获取指示当前流是否支持查找功能的值。
        /// </summary>
        /// <returns>
        /// 如果流支持查找，为 true；否则为 false。
        /// </returns>
        public override bool CanSeek
        {
            get { return false; }
        }

        /// <summary>
        /// 当在派生类中重写时，获取指示当前流是否支持写入功能的值。
        /// </summary>
        /// <returns>
        /// 如果流支持写入，则为 true；否则为 false。
        /// </returns>
        public override bool CanWrite
        {
            get { return false; }
        }

        /// <summary>
        /// 当在派生类中重写时，获取流长度（以字节为单位）。
        /// </summary>
        /// <returns>
        /// 表示流长度（以字节为单位）的长值。
        /// </returns>
        /// <exception cref="T:System.NotSupportedException">从 Stream 派生的类不支持查找。</exception><exception cref="T:System.ObjectDisposedException">在流关闭后调用方法。</exception>
        public override long Length
        {
            get { return mLength; }
        }

        /// <summary>
        /// 当在派生类中重写时，获取或设置当前流中的位置。
        /// </summary>
        /// <returns>
        /// 流中的当前位置。
        /// </returns>
        /// <exception cref="T:System.IO.IOException">发生 I/O 错误。</exception><exception cref="T:System.NotSupportedException">流不支持查找。</exception><exception cref="T:System.ObjectDisposedException">在流关闭后调用方法。</exception>
        public override long Position
        {
            get { return mBaseStream.Position; }
            set { throw new NotSupportedException(); }
        }

        public DecryptionStream(Stream cipherStream)
        {
            if (cipherStream.Length > mZipHeader.Length)
            {
                if (checkHeader(cipherStream, mPlainHeader))
                {
                    //未压缩、已加密文件
                    //取出文件长度
                    cipherStream.Read(mLengthBuffer, 0, mLengthBuffer.Length);
                    mLength = BitConverter.ToInt32(mLengthBuffer, 0);
                    mBaseStream = new OutputStream(Key, Offset, cipherStream);
                    return;
                }

                if (checkHeader(cipherStream, mZipHeader))
                {
                    //已压缩、已加密文件
                    //取出文件长度
                    cipherStream.Read(mLengthBuffer, 0, mLengthBuffer.Length);
                    mLength = BitConverter.ToInt32(mLengthBuffer, 0);
                    var zipStream = new ZipInputStream(new OutputStream(Key, Offset, cipherStream));
                    zipStream.GetNextEntry();
                    mBaseStream = zipStream;
                    return;
                }
            }

            cipherStream.Seek(0, SeekOrigin.Begin);
            mBaseStream = cipherStream;
            mLength = mBaseStream.Length;
        }

        /// <summary>
        /// 当在派生类中重写时，将清除该流的所有缓冲区，并使得所有缓冲数据被写入到基础设备。
        /// </summary>
        /// <exception cref="T:System.IO.IOException">发生 I/O 错误。</exception>
        public override void Flush()
        {
        }

        /// <summary>
        /// 当在派生类中重写时，从当前流读取字节序列，并将此流中的位置提升读取的字节数。
        /// </summary>
        /// <returns>
        /// 读入缓冲区中的总字节数。如果很多字节当前不可用，则总字节数可能小于请求的字节数；如果已到达流结尾，则为零 (0)。
        /// </returns>
        /// <param name="buffer">字节数组。此方法返回时，该缓冲区包含指定的字符数组，该数组的 <paramref name="offset"/> 和 (<paramref name="offset"/> + <paramref name="count"/> -1) 之间的值由从当前源中读取的字节替换。</param><param name="offset"><paramref name="buffer"/> 中的从零开始的字节偏移量，从此处开始存储从当前流中读取的数据。</param><param name="count">要从当前流中最多读取的字节数。</param><exception cref="T:System.ArgumentException"><paramref name="offset"/> 与 <paramref name="count"/> 的和大于缓冲区长度。</exception><exception cref="T:System.ArgumentNullException"><paramref name="buffer"/> 为 null。</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="offset"/> 或 <paramref name="count"/> 为负。</exception><exception cref="T:System.IO.IOException">发生 I/O 错误。</exception><exception cref="T:System.NotSupportedException">流不支持读取。</exception><exception cref="T:System.ObjectDisposedException">在流关闭后调用方法。</exception>
        public override int Read(byte[] buffer, int offset, int count)
        {
            return mBaseStream.Read(buffer, offset, count);
        }

        /// <summary>
        /// 当在派生类中重写时，设置当前流中的位置。
        /// </summary>
        /// <returns>
        /// 当前流中的新位置。
        /// </returns>
        /// <param name="offset">相对于 <paramref name="origin"/> 参数的字节偏移量。</param><param name="origin"><see cref="T:System.IO.SeekOrigin"/> 类型的值，指示用于获取新位置的参考点。</param><exception cref="T:System.IO.IOException">发生 I/O 错误。</exception><exception cref="T:System.NotSupportedException">流不支持查找，例如在流通过管道或控制台输出构造的情况下即为如此。</exception><exception cref="T:System.ObjectDisposedException">在流关闭后调用方法。</exception>
        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// 当在派生类中重写时，设置当前流的长度。
        /// </summary>
        /// <param name="value">所需的当前流的长度（以字节表示）。</param><exception cref="T:System.IO.IOException">发生 I/O 错误。</exception><exception cref="T:System.NotSupportedException">流不支持写入和查找，例如在流通过管道或控制台输出构造的情况下即为如此。</exception><exception cref="T:System.ObjectDisposedException">在流关闭后调用方法。</exception>
        public override void SetLength(long value)
        {
            throw new NotSupportedException();
        }

        /// <summary>
        /// 当在派生类中重写时，向当前流中写入字节序列，并将此流中的当前位置提升写入的字节数。
        /// </summary>
        /// <param name="buffer">字节数组。此方法将 <paramref name="count"/> 个字节从 <paramref name="buffer"/> 复制到当前流。</param><param name="offset"><paramref name="buffer"/> 中的从零开始的字节偏移量，从此处开始将字节复制到当前流。</param><param name="count">要写入当前流的字节数。</param><exception cref="T:System.ArgumentException">总和 <paramref name="offset"/> 和 <paramref name="count"/> 大于缓冲区长度。</exception><exception cref="T:System.ArgumentNullException"><paramref name="buffer"/>  是 null。</exception><exception cref="T:System.ArgumentOutOfRangeException"><paramref name="offset"/> 或 <paramref name="count"/> 为负。</exception><exception cref="T:System.IO.IOException">将出现 I/O 错误，如找不到指定的文件。</exception><exception cref="T:System.NotSupportedException">流不支持写入。</exception><exception cref="T:System.ObjectDisposedException"><see cref="M:System.IO.Stream.Write(System.Byte[],System.Int32,System.Int32)"/> 流关闭后调用。</exception>
        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotSupportedException();
        }

        private static bool checkHeader(Stream stream, byte[] headerToCheck)
        {
            stream.Seek(0, SeekOrigin.Begin);
            stream.Read(mHeaderBuffer, 0, mHeaderBuffer.Length);
            for (var i = 0; i < mHeaderBuffer.Length; i++)
            {
                if (mHeaderBuffer[i] != headerToCheck[i])
                    return false;
            }
            return true;
        }

        private readonly Stream mBaseStream;
        private static readonly byte[] mLengthBuffer = new byte[4];
        private static readonly byte[] mZipHeader = Encoding.ASCII.GetBytes("CWS");
        private static readonly byte[] mPlainHeader = Encoding.ASCII.GetBytes("FWS");
        private static readonly byte[] mHeaderBuffer = new byte[3];
        private readonly long mLength;
    }
}